using UnityEngine;
using System.Collections;
using System.Collections.Generic;

public class GameEngine : MonoBehaviour {
	
	public delegate void GameEngineEvent();
	
	public event GameEngineEvent EventGotGold;
	public event GameEngineEvent EventGotHome;
	
	public int gridWidth = 100;
	public int gridHeight = 40;
	public int maxPolice = 8;
	public float gasExplodeTimer = 2f;
	public float blockRestoreTime = 30f;
	public float blockRestoreDistance = 4f;
	public Player player;
	public DonutMesh vignette;
	
	public AudioSource music1;
	public AudioSource music2;
	
	public Vector2 bordersHouse = new Vector2(0,5);
	public Vector2 bordersBank = new Vector2(90,96);
	
	public Transform prefabBlock;
	public Transform prefabPolice;
	public Transform prefabExplosion;
	public Transform prefabBombRevealed;
	public Transform bank;
	public Transform vault;
	
	public int goldAvailable { get; private set; }
	public int goldCollected { get; private set; }
	public Rect worldBounds { get; private set; }
	public Rect blockBounds { get; private set; }
	
	private Gold _gold;
	private int _goldTriggerCount;
	
	private QuadTree _tree;
	private Block[,] _data;
	private bool[,] _impassable;
	private List<Police> _police;
	private List<Block> _destroyedBlocks;
	private List<Transform> _destroyObjects;
	
	public List<IQuadObject> Search(Rect bounds){
		return _tree.Query(bounds);	
	}
	public Block GetBlockAt(int x, int y){
		if(x<0 || x>=gridWidth || y<0 || y>=gridHeight) return null;
		
		return _data[x,y];
	}
	public Vector2 GetGridPosition(Vector3 worldPosition){
		float gx = worldPosition.x/blockBounds.width;
		float gy = worldPosition.y/blockBounds.height;
		
		return new Vector2(Mathf.Round(gx),Mathf.Round(gy));
	}
	public void Reset(){
		bank.animation.Stop();
		bank.animation.Rewind();
		bank.animation.Sample();
		
		_goldTriggerCount = 0;
		goldCollected = 0;
		StopAllCoroutines();
		
		goldAvailable = Random.Range(200,400);
		player.Reset(true);
		player.transform.position = new Vector3(blockBounds.width*(Mathf.Lerp(bordersHouse.x,bordersHouse.y,0.5f)),worldBounds.yMax+0.5f,-0.1f);
		
		foreach(Police p in _police) if(p!=null) Destroy(p.gameObject);
		_police = new List<Police>();
		
		foreach(Transform t in _destroyObjects) Destroy(t.gameObject);
		_destroyObjects = new List<Transform>();
		
		_destroyedBlocks = new List<Block>();
		int x=0, y=0;
		for(; x<gridWidth; ++x){
			for(y=0; y<gridHeight; ++y){
				Block b = _data[x,y];	
				b.Setup(GetTypeForBlock(b.x,b.y),b.x,b.y,this);
				b.gameObject.SetActiveRecursively(true);
			}
		}	
	}
	public AbstractElement CreateElement(System.Type type, Vector3 position, Rect bounds, Transform prefab = null){
		GameObject go = null;
		if(prefab!=null){
			go = Instantiate(prefab) as GameObject;	
		} else {
			go = new GameObject();	
		}
		go.name = "Element_"+type;
		if(go.GetComponent(type)==null) go.AddComponent(type);
		go.transform.parent = transform;
		go.transform.position = position;
		
		AbstractElement e = go.GetComponent(type) as AbstractElement;
		e.Create(bounds);
		_tree.Insert(e);
		
		return e;
	}
	
	protected void Awake(){
		Application.targetFrameRate = 60;
		
		_data = new Block[gridWidth,gridHeight];
		_impassable = new bool[gridWidth,gridHeight];
		_tree = new QuadTree(new Vector2(0.01f,0.01f),1);
		_destroyedBlocks = new List<Block>();
		_destroyObjects = new List<Transform>();
		_goldTriggerCount = 0;
		_police = new List<Police>();
		
		player.EventDead += HandlePlayerEventDead;
		worldBounds = new Rect();
			
		// create blocks
		int x=0, y=0;
		for(; x<gridWidth; ++x){
			for(y=0; y<gridHeight; ++y){
				Transform bt = Instantiate(prefabBlock) as Transform;
				bt.transform.parent = transform;
				
				BlockType type = GetTypeForBlock(x,y);
				
				Block b = bt.GetComponent<Block>();
				b.Setup(type,x,y,this);
				b.EventDestroyed += HandleBlockEventDestroyed;
					
				_tree.Insert(b);
				_data[x,y] = b;
				blockBounds = b.localBounds;
				
				worldBounds = UtilsRect.addRects(worldBounds,b.worldBounds);
			}
		}
		
		player.SetBounds(blockBounds);
		
		bank.transform.position = new Vector3(blockBounds.width*Mathf.Lerp(bordersBank.x,bordersBank.y,0.5f),worldBounds.yMax-0.05f,0.5f);
		vault.position = new Vector3(blockBounds.width*bordersHouse.x,worldBounds.yMax+0.15f,0.2f);
		
		// create walls
		Rect wallBounds = new Rect(blockBounds.x,0,blockBounds.width,2);
		CreateElement(typeof(Wall),new Vector3(blockBounds.width*(bordersHouse.x-1),worldBounds.yMax,0),wallBounds);
		CreateElement(typeof(Wall),new Vector3(blockBounds.width*(bordersHouse.y+1),worldBounds.yMax,0),wallBounds);
		CreateElement(typeof(Wall),new Vector3(blockBounds.width*(bordersBank.x),worldBounds.yMax,0),wallBounds);
		CreateElement(typeof(Wall),new Vector3(blockBounds.width*(bordersBank.y),worldBounds.yMax,0),wallBounds);
		
		// create triggers
		Trigger houseTrigger = CreateElement(typeof(Trigger),new Vector3((bordersHouse.x-0.5f)*blockBounds.width,worldBounds.yMax,0),new Rect(0,0,blockBounds.width*(Mathf.Abs(bordersHouse.x-bordersHouse.y)+1),2)) as Trigger;
		houseTrigger.EventTriggered += HandleHouseTriggerEventTriggered;
		
		// create gold
		_gold = CreateElement(typeof(Gold),new Vector3((bordersBank.x-0.5f)*blockBounds.width,worldBounds.yMax,0),new Rect(0,0,blockBounds.width*(Mathf.Abs(bordersBank.x-bordersBank.y)+1),2)) as Gold;
		_gold.EventTriggered += HandleGoldEventTriggered;
		
		player.transform.position = new Vector3(blockBounds.width*(Mathf.Lerp(bordersHouse.x,bordersHouse.y,0.5f)),worldBounds.yMax+0.5f,-0.1f);
	}
	
	protected void FixedUpdate(){
		Vector2 gp = GetGridPosition(player.transform.position);
		float dy = gridHeight-gp.y;
		if(dy<0) dy = 0;
		dy /= 4;
		if(dy>1) dy = 1;
		vignette.alphaOuter = Mathf.Lerp(0,1f,dy);
		vignette.updateInner();
		
		// restore destroyed blocks
		int i=_destroyedBlocks.Count-1;
		for(; i>=0; --i){
			Block b = _destroyedBlocks[i];
			
			float a = b.x-gp.x;
			float o = b.y-gp.y;
			float d = Mathf.Sqrt(a*a+o*o);
			if(Time.time-b.blockDestroyedTime>=blockRestoreTime && d>=blockRestoreDistance && !b.IsLadder){
				b.Setup(GetTypeForBlock(b.x,b.y,false),b.x,b.y,this);
				b.gameObject.SetActiveRecursively(true);
				
				_destroyedBlocks.Remove(b);
			}
		}
	}
	
	private BlockType GetTypeForBlock(int x, int y, bool canBeImpassable = true){
		if(x==0 || y==0 || x==gridWidth-1 || y==gridHeight-1 || _impassable[x,y]==true){
			return !_impassable[x,y]==true && y==gridHeight-1 && ((x>bordersHouse.x && x<bordersHouse.y) || (x>bordersBank.x && x<bordersBank.y)) ? BlockType.Dirt : BlockType.Impassable;
		}
		
		float d = 1-((float)y/(float)gridHeight);
		d -= 0.3f;
		if(d<0) d = 0;
		d /= 0.7f;
		
		float sp1_1 = Mathf.Lerp(0.4f,0.62f,d);
		float sp1_2 = Mathf.Lerp(0.35f,0.7f,d);
		float sp2_1 = Mathf.Lerp(0.89f,0.93f,d);
		float sp2_2 = Mathf.Lerp(0.35f,0.6f,d);
		
		BlockType type = BlockType.Dirt;
		if(Random.Range(0.0f,1.0f)>0.93f && canBeImpassable){
			type = BlockType.Impassable;
		} else if(Random.Range(0.0f,1.0f)>sp1_1){
			type = Random.Range(0.0f,1.0f)>sp1_2 ? BlockType.Concrete : BlockType.Sand;
		} else if(Random.Range(0.0f,1.0f)>sp2_1){
			type = Random.Range(0.0f,1.0f)>sp2_2 || _goldTriggerCount<1 ? BlockType.GasVisible : BlockType.Gas;	
		}	
		
		return type;
	}

	private void HandlePlayerEventDead (AbstractCharacter player){
		Invoke("Reset",2f);
	}
	private void HandlePoliceEventDead (AbstractCharacter police){
		if(_police.Contains(police as Police)){
			_police.Remove(police as Police);
			Destroy(police.gameObject);	
		}
	}
	private void HandleBlockEventDestroyed (Block b){
		if(!_destroyedBlocks.Contains(b)) _destroyedBlocks.Add(b);
		
		if(!player.IsDead && (b.type==BlockType.Gas || b.type==BlockType.GasVisible)){
			StartCoroutine(GasExplosion(b, 2, gasExplodeTimer));
		} else {
			b.gameObject.SetActiveRecursively(false);
		}
	}
	private void HandleHouseTriggerEventTriggered (Trigger trigger){
		if(!music1.isPlaying){
			music1.Play();
			music2.Stop();
		}
		
		goldCollected += player.gold;
		goldAvailable = Random.Range(200,400);
		player.Reset();
		_gold.Reset();
		
		if(EventGotHome!=null) EventGotHome();
	}
	private void HandleGoldEventTriggered (Trigger trigger){
		if(!music2.isPlaying){
			music2.Play();
			music1.Stop();
		}
		
		player.CollectGold(goldAvailable);
		goldAvailable = 0;
		
		if(_goldTriggerCount==0) bank.animation.Play();
		_goldTriggerCount++;
		
		if(EventGotGold!=null) EventGotGold();
		
		// add police
		if(_destroyedBlocks.Count>0){
			int i=0, l=20;
			Block b = _destroyedBlocks[Random.Range(0,_destroyedBlocks.Count)];
			while((b.x<=bordersHouse.y+3 || b.x>=bordersBank.x-3) && i<l){
				b = _destroyedBlocks[Random.Range(0,_destroyedBlocks.Count)];
				++i;	
			}
			
			if(i<20){
				Police p = (Instantiate(prefabPolice,b.transform.position,Quaternion.Euler(0,90,0)) as Transform).GetComponent<Police>();
				p.Setup(blockBounds,this);
				p.EventDead += HandlePoliceEventDead;
				_police.Add(p);
			}
		}
		if(_police.Count>=maxPolice){
			_police[Random.Range(0,_police.Count-1)].Kill();	
		}
	}
	
	private IEnumerator GasExplosion(Block b, int explosionrange, float timeUntilExplosion){
		b.gameObject.SetActiveRecursively(false);
		
		Transform bomb = Instantiate(prefabBombRevealed,b.transform.position,Quaternion.identity) as Transform;
		_destroyObjects.Add(bomb);
		
		yield return new WaitForSeconds(timeUntilExplosion);
		
		_destroyObjects.Remove(bomb);
		Destroy(bomb.gameObject);
		
		if(!DestroyBlocks(b.transform.position, explosionrange)) b.gameObject.SetActiveRecursively(false);
	}
	
	public bool DestroyBlocks (Vector3 positionOfExplosionOrigin, int explosionrange){
		Instantiate(prefabExplosion,positionOfExplosionOrigin,Quaternion.identity);
		
		Vector2 gp = GetGridPosition(player.transform.position);
		Vector2 gridPositionOfExplosion = GetGridPosition(positionOfExplosionOrigin);
		
		bool playerDead = false;
		int x = (int)gridPositionOfExplosion.x-explosionrange, y=(int)gridPositionOfExplosion.y-explosionrange;
		int xl=(int)gridPositionOfExplosion.x+explosionrange, yl=(int)gridPositionOfExplosion.y+explosionrange;
		for(; x<xl && !playerDead; ++x){
			for(y=(int)gridPositionOfExplosion.y-explosionrange; y<yl && !playerDead; ++y){
				
				if(x==gp.x && y==gp.y){
					playerDead = true;
				}
				
				int i=_police.Count-1;
				for(; i>=0; --i){
					Police p = _police[i];
					Vector2 pgp = GetGridPosition(p.transform.position);	
					if(pgp.x==x && pgp.y==y) p.Kill();
				}
				
				Block bd = GetBlockAt(x,y);
				if(bd==null || bd.IsDestroyed) continue;
				
				float a = gridPositionOfExplosion.x-x;
				float o = gridPositionOfExplosion.y-y;
				float d = Mathf.Sqrt(a*a+o*o);
				if(d<=1 || Random.Range(0.0f,1.0f)>0.3f) bd.Destroy();
			}
		}
		
		if(playerDead){
			if(!player.IsDead) player.Kill();
			return true;
		} else {
			return false;	
		}
	}
}
